<template>
  <div class="code-editor">
    <Codemirror
      v-model:value="innerValue"
      :options="editorOptions"
      :height="height"
      :width="width"
      border
      ref="cmRef"
      @change="onChange"
      @input="onInput"
      @ready="onReady"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import Codemirror from 'codemirror-editor-vue3'
import type { CmComponentRef } from 'codemirror-editor-vue3'
import type { Editor, EditorConfiguration } from 'codemirror'
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/mode/xml/xml.js'
import 'codemirror/mode/sql/sql.js'
import 'codemirror/theme/monokai.css'
import 'codemirror/theme/material.css'

interface Props {
  modelValue?: string
  mode?: string
  theme?: string
  height?: number | string
  width?: number | string
  readOnly?: boolean
  lineNumbers?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: '',
  mode: 'application/json',
  theme: 'default',
  height: 400,
  width: '100%',
  readOnly: false,
  lineNumbers: true,
})

const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
  (e: 'change', value: string, editor: Editor): void
  (e: 'ready', editor: Editor): void
}>()

const cmRef = ref<CmComponentRef>()
const innerValue = ref(props.modelValue)

// 编辑器配置
const editorOptions: EditorConfiguration = {
  mode: props.mode,
  theme: props.theme,
  lineNumbers: props.lineNumbers,
  readOnly: props.readOnly,
  tabSize: 2,
  gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
  extraKeys: {
    'Ctrl-Space': 'autocomplete',
  },
}

// 监听值变化
watch(
  () => props.modelValue,
  (newVal) => {
    if (newVal !== innerValue.value) {
      innerValue.value = newVal
    }
  },
)

// 编辑器事件处理
const onChange = (val: string, cm: Editor) => {
  emit('update:modelValue', val)
  emit('change', val, cm)
}

const onInput = (val: string) => {
  emit('update:modelValue', val)
}

const onReady = (cm: Editor) => {
  emit('ready', cm)
}

// 对外暴露的方法
defineExpose({
  /**
   * 刷新编辑器
   */
  refresh: () => {
    cmRef.value?.refresh()
  },
  /**
   * 调整编辑器大小
   */
  resize: (width: number, height: number) => {
    cmRef.value?.resize(width, height)
  },
  /**
   * 获取编辑器实例
   */
  getEditor: () => {
    return cmRef.value?.cminstance
  },
  /**
   * 设置编辑器值
   */
  setValue: (value: string) => {
    innerValue.value = value
  },
  /**
   * 获取编辑器值
   */
  getValue: () => {
    return cmRef.value?.cminstance.getValue()
  },
})
</script>

<style scoped>
.code-editor {
  border: 1px solid var(--el-border-color);
  border-radius: 4px;
}

:deep(.cm-s-default) {
  font-family: Consolas, Monaco, monospace;
  font-size: 14px;
  line-height: 1.5;
}

:deep(.CodeMirror) {
  height: v-bind(height + 'px');
}
</style>
